package com.ColonyCount.Image;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.security.InvalidParameterException;
import java.util.List;



import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Environment;
import android.util.Log;

public class ImageHelper {
	
	private ImageHelper()
	{
	
	}
	
	
	/** 
	 * Fills the selected pixel and all surrounding pixels of the same color with the fill color. 
	 * @param img image on which operation is applied 
	 * @param fillColor color to be filled in 
	 * @param loc location at which to start fill 
	 * @throws IllegalArgumentException if loc is out of bounds of the image 
	 */  
	public static int floodFill(Image8 img, int x, int y, int fillValue) {  
	  if (x < 0 || x >= img.getWidth() || y < 0 || y >= img.getHeight()) 
		  throw new IllegalArgumentException();  
	    
	  int oldValue = img.getPixelValue(x,y);

	  // Checks trivial case where loc is of the fill color  
	  if (fillValue == oldValue) 
		  return 0;  
	    
	  int size = floodLoop(img, x, y, fillValue, oldValue);  
	  return size;
	}  
	  
	// Recursively fills surrounding pixels of the old color  
	private static int floodLoop(Image8 img, int x, int y, int fillValue, int oldValue) {  

		int size = 0;
		
		// finds the left side, filling along the way  
		int fillL = x;  
		do {  
			img.setPixelValue(fillL, y, fillValue);
			size ++;
		    fillL--;  
		} while (fillL >= 0 && (img.getPixelValue(fillL, y) == oldValue));  
		fillL++;  
		    
		// find the right right side, filling along the way  
		int fillR = x;  
		do {  
			img.setPixelValue(fillR, y, fillValue);  
			size ++;
		    fillR++;  
		 } while (fillR < img.getWidth() - 1 && (img.getPixelValue(fillR, y) == oldValue));  
		 fillR--;  
		    
		 // checks if applicable up or down  
		 for (int i = fillL; i <= fillR; i++) 
		 {  
			 if (y > 0 && (img.getPixelValue(i, y - 1) ==  oldValue)) 
				 size += floodLoop(img, i, y - 1, fillValue, oldValue);  
			 if (y < img.getHeight() - 1 && (img.getPixelValue(i, y + 1) == oldValue)) 
				 size += floodLoop(img, i, y + 1, fillValue, oldValue);  
		 }  
		 return size;
	}  
	  
	// Recursively fills surrounding pixels of the old color  
	public static int floodLoop(Image8 img, int x, int y, int fillValue, int oldValue, List<Point> points) {  

		int size = 0;
		
		// finds the left side, filling along the way  
		int fillL = x;  
		do {  
			img.setPixelValue(fillL, y, fillValue);
			points.add(new Point(fillL,y));
			size ++;
		    fillL--;  
		} while (fillL >= 0 && (img.getPixelValue(fillL, y) == oldValue));  
		fillL++;  
		    
		// find the right right side, filling along the way  
		int fillR = x;  
		do {  
			img.setPixelValue(fillR, y, fillValue);  
			points.add(new Point(fillR,y));
			size ++;
		    fillR++;  
		 } while (fillR < img.getWidth() - 1 && (img.getPixelValue(fillR, y) == oldValue));  
		 fillR--;  
		    
		 // checks if applicable up or down  
		 for (int i = fillL; i <= fillR; i++) 
		 {  
			 if (y > 0 && (img.getPixelValue(i, y - 1) ==  oldValue)) 
				 size += floodLoop(img, i, y - 1, fillValue, oldValue, points);  
			 if (y < img.getHeight() - 1 && (img.getPixelValue(i, y + 1) == oldValue)) 
				 size += floodLoop(img, i, y + 1, fillValue, oldValue, points);  
		 }  
		 return size;
	}  	
	
	
	public static void loadLumaFromARGB(Image32 sourceImage, Image16 targetMap)
	{
		long time = System.currentTimeMillis();
		
		if(sourceImage.getWidth() != targetMap.getWidth() || sourceImage.getHeight() != targetMap.getHeight())
		{
			throw new InvalidParameterException("source and target not same size");
		}

		targetMap.maxValue = 255;
		
		// create pixel buffer
		int[] sourcePixels = sourceImage.pixels;
		short[] targetPixels = targetMap.pixels;
		int size = sourcePixels.length;
		for (int i = 0; i < size; i++) {
			int p = sourcePixels[i];
			int r = (p & 0xff0000) >> 16;
			int g = (p & 0xff00) >> 8;
			int b = p & 0xff;
			
			// Calc Luma
			float fVal = 0.299f * (float)r + 0.587f * (float)g + 0.114f * (float)b;
			short iVal = (short)fVal;
			targetPixels[i] = iVal;	
		}
		
		Log.v("", "Image.convertARGBToLuma " + (System.currentTimeMillis() - time)+ "ms");
	}
	
	public static void loadLumaFromARGB(Image32 sourceImage, Image8 lumaMap)
	{
		long time = System.currentTimeMillis();
		
		if(sourceImage.getWidth() != lumaMap.getWidth() || sourceImage.getHeight() != lumaMap.getHeight())
		{
			throw new InvalidParameterException("source and target not same size");
		}

		// create pixel buffer
		int[] sourcePixels = sourceImage.pixels;
		byte[] targetPixels = lumaMap.pixels;
		int size = sourcePixels.length;
		for (int i = 0; i < size; i++) {
			int p = sourcePixels[i];
			int r = (p & 0xff0000) >> 16;
			int g = (p & 0xff00) >> 8;
			int b = p & 0xff;
			
			// Calc Luma
			float fVal = 0.299f * (float)r + 0.587f * (float)g + 0.114f * (float)b;
			int iVal = (int)fVal;
			targetPixels[i] = (byte)(iVal & 0xFF);
		}
		
		Log.v("", "Image.convertARGBToLuma " + (System.currentTimeMillis() - time)+ "ms");
	}
	
	public static void loadHistogram(Image16 imageMap, Histogram histogram)
	{
		long time = System.currentTimeMillis();
		
		if(imageMap.maxValue >= histogram.values.length)
		{
			throw new InvalidParameterException("Histogram not big enough to hold image values");
		}
		
		short[] sourcePixels = imageMap.pixels;
		int[] histogramValues = histogram.values;
		int imageSize = imageMap.getWidth()*imageMap.getHeight();
		
		for (int i = 0; i < imageSize; i++) 
		{
			short iVal = sourcePixels[i];
			histogramValues[iVal] ++;
		}
		
		int max = 0;
		int histrogramSize = histogramValues.length;
		for (int i = 0; i < histrogramSize; i++) 
		{
			int count = histogramValues[i];
			if(count > max)
			{
				max = count;
			}
		}
		histogram.maxValue = max;
		
		Log.v("", "Image.loadHistogram " + (System.currentTimeMillis() - time)+ "ms");
	}
	
	public static void loadHistogram(Image8 imageMap, Histogram histogram)
	{
		long time = System.currentTimeMillis();
		
		byte[] sourcePixels = imageMap.getPixels();
		int[] histogramValues = histogram.values;
		int imageSize = imageMap.getWidth()*imageMap.getHeight();
		
		for (int i = 0; i < imageSize; i++) 
		{
			int iVal = sourcePixels[i] & 0xff;
			histogramValues[iVal] ++;
		}
		
		// We ignore value 255 as it is used as mask
		histogramValues[255] = 0;
		
		int max = 0;
		int histrogramSize = histogramValues.length;
		for (int i = 0; i < histrogramSize; i++) 
		{
			int count = histogramValues[i];
			if(count > max)
			{
				max = count;
			}
		}
		histogram.maxValue = max;
		
		Log.v("", "Image.loadHistogram " + (System.currentTimeMillis() - time)+ "ms");
	}	
	
	public static void convertARGBToLuma(Image32 image,int[] histogram)
	{
		if(histogram.length != 256)
		{
			throw new InvalidParameterException("Histogram does not have length 256");
		}
		
		// create pixel buffer
		int[] pixels = image.pixels;
		int size = pixels.length;
		for (int i = 0; i < size; i++) {
			int p = pixels[i];
			int r = (p & 0xff0000) >> 16;
			int g = (p & 0xff00) >> 8;
			int b = p & 0xff;
			
			// Calc Luma
			float fVal = 0.299f * (float)r + 0.587f * (float)g + 0.114f * (float)b;
			int iVal = (int)fVal;
			
			pixels[i] = 0xff000000 | iVal << 16 | iVal << 8 | iVal;	
			histogram[iVal] ++;
		}
	}
	
	public static void loadARGBFromBitmap(Bitmap bitmap, Image32 image)
	{
		long startTime = System.currentTimeMillis();

		if(image.getWidth() != bitmap.getWidth() || image.getHeight() != bitmap.getHeight())
		{
			throw new InvalidParameterException("Image and bitmap not same size");
		}
		
		// Check size and target size

		Bitmap.Config config = bitmap.getConfig();
		switch(config)
		{
	
		case ARGB_8888:
		{
			IntBuffer pixels = IntBuffer.wrap(image.pixels);
			bitmap.copyPixelsToBuffer(pixels);
			break;
		}
		default:
			throw new IllegalArgumentException("Unsupported image type: " + config);
		}
		
		Log.v("", "Image.loadARGBFromBitmap " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	public static void getOverlayFromLuma(final Image32 sourceImage, int thresholdMin, int thresholdMax, Image32 targetImage)
	{
		long time = System.currentTimeMillis();
		
		int count = sourceImage.pixels.length;
		int[] sourcePixels = sourceImage.pixels;
		int[] overlayPixels = targetImage.pixels;
		for(int i = 0; i < count;i++)
		{
			int iVal = sourcePixels[i] & 0x000000FF;
			
			if(iVal > thresholdMax) 
			{
				overlayPixels[i] = 0x00000000;
				continue;
			}

			if(iVal < thresholdMin) 
			{
				overlayPixels[i] = 0x00000000;
				continue;
			}

			overlayPixels[i] = 0xCC55FF55;
		}
		
		Log.v("", "update overlay image" + (System.currentTimeMillis() - time) + "ms");
	}
	
	public static void loadBitmapFromARGB(Image32 image, Bitmap bitmap)
	{
		long startTime = System.currentTimeMillis();
		
		if(image.getWidth() != bitmap.getWidth() || image.getHeight() != bitmap.getHeight())
		{
			throw new InvalidParameterException("Image and bitmap not same size");
		}
		
		bitmap.setPixels(image.pixels, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight());
		
		Log.v("", "Image.loadBitmapFromARGB " + (System.currentTimeMillis() - startTime) + "ms");
	}	
	
	public static void loadLumaFromImage(Image32 sourceImage, Image32 lumaImage, int[] histogram)
	{
		long startTime = System.currentTimeMillis();
		
		if(sourceImage.getWidth() != sourceImage.getWidth() || lumaImage.getHeight() != lumaImage.getHeight())
		{
			throw new InvalidParameterException("Image and bitmap not same size");
		}
		if(histogram.length != 256)
		{
			throw new InvalidParameterException("Histogram does not have length 256");
		}
		
		int[] sourcePixels = sourceImage.pixels;
		int[] lumaPixels = lumaImage.pixels;
		int size = sourcePixels.length;
		for (int i = 0; i < size; i++) {
			int p = sourcePixels[i];
			int r = (p & 0xff0000) >> 16;
			int g = (p & 0xff00) >> 8;
			int b = p & 0xff;
			
			// Calc Luma
			float fVal = 0.299f * (float)r + 0.587f * (float)g + 0.114f * (float)b;
			int iVal = (int)fVal;
			lumaPixels[i] = iVal;
			histogram[iVal] ++;
		}

		Log.v("", "Image.loadLumaFromImage " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	
	
	public static void loadARGBFromLuma(Image32 lumaImage, Image32 lumaImageARGB)
	{
		long startTime = System.currentTimeMillis();
		
		int[] lumaPixels = lumaImage.pixels;
		int[] lumaPixelsARGB = lumaImageARGB.pixels;
		for (int i = 0; i < lumaPixels.length; i++) {
			int iVal = lumaPixels[i];
			lumaPixelsARGB[i] = 0xff000000 | iVal << 16 | iVal << 8 | iVal;	
		}
		
		Log.v("", "Image.lumaPixelsARGB " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	public static void loadARGBFromLuma(Image8 lumaImage, Image32 lumaImageARGB)
	{
		long startTime = System.currentTimeMillis();
		
		byte[] lumaPixels = lumaImage.pixels;
		int[] lumaPixelsARGB = lumaImageARGB.pixels;
		for (int i = 0; i < lumaPixels.length; i++) {
			int iVal = lumaPixels[i] & 0xFF;
			lumaPixelsARGB[i] = 0xff000000 | iVal << 16 | iVal << 8 | iVal;	
		}
		
		Log.v("", "Image.lumaPixelsARGB " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	public static void loadARGBFromPalette(Image8 image, int[] palette, Image32 lumaImageARGB)
	{
		long startTime = System.currentTimeMillis();
		
		byte[] lumaPixels = image.pixels;
		int[] lumaPixelsARGB = lumaImageARGB.pixels;
		for (int i = 0; i < lumaPixels.length; i++) {
			int iVal = lumaPixels[i] & 0xFF;
			lumaPixelsARGB[i] = palette[iVal];	
		}
		
		Log.v("", "Image.lumaPixelsARGB " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	public static void savBitmap(Bitmap bitmap, String name)
	{
		File root = Environment.getExternalStorageDirectory();
		File file = new File(root, name);
		if(!file.exists())
		{
			try {
				file.createNewFile();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		FileOutputStream out;
		try {
			 out = new FileOutputStream(file);
			 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void loadARGBFromIntensity(Image8 sourceMap, Image32 targetImage)
	{
		long startTime = System.currentTimeMillis();
		
		if(targetImage.getWidth() != sourceMap.getWidth() || targetImage.getHeight() != sourceMap.getHeight())
		{
			throw new InvalidParameterException("targetImage and sourceMap not same size");
		}
		
		byte[] lumaPixels = sourceMap.pixels;
		int[] lumaPixelsARGB = targetImage.pixels;
		int count = lumaPixels.length;

		for (int i = 0; i < count; i++) {
			int iVal = lumaPixels[i] & 0xff;
			lumaPixelsARGB[i] = 0xff000000 | iVal << 16 | iVal << 8 | iVal;	
		}
		
		Log.v("", "loadARGBFromIntensity " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	public static void loadBitmapFromIntensity(Image8 sourceMap, Bitmap targetBitmap)
	{
		long startTime = System.currentTimeMillis();
		
		if(targetBitmap.getWidth() != sourceMap.getWidth() || targetBitmap.getHeight() != sourceMap.getHeight())
		{
			throw new InvalidParameterException("targetBitmap and sourceMap not same size");
		}
		
		byte[] lumaPixels = sourceMap.pixels;
		int count = lumaPixels.length;
		int[] lumaPixelsARGB = new int[count];
		for (int i = 0; i < count; i++) {
			int iVal = lumaPixels[i] & 0xff;
			lumaPixelsARGB[i] = 0xff000000 | iVal << 16 | iVal << 8 | iVal;	
		}

		targetBitmap.setPixels(lumaPixelsARGB, 0, sourceMap.getWidth(), 0, 0, sourceMap.getWidth(), sourceMap.getHeight());
		
		Log.v("", "loadARGBFromIntensity " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	public static void loadLumaFromBitmap(Bitmap bitmap, Image8 luma, Rect region)
	{
		long startTime = System.currentTimeMillis();
		
		// Check size and target size
		byte[] lumaPixels = luma.pixels;

		Bitmap.Config config = bitmap.getConfig();
		switch(config)
		{
		case RGB_565:
		{
			// Build conversion table
			int[] rbTable = new int[32];
			int[] gTable = new int[64];
			for (int i=0; i<32; i++)
				rbTable[i] = 255*i/31;
			for (int i=0; i<64; i++)
				gTable[i] = 255*i/63;
			
			int bitmapSize = bitmap.getWidth()*bitmap.getHeight();
			short[] bitmapPixels = new short[bitmapSize];
			ShortBuffer bitmapBuffer = ShortBuffer.wrap(bitmapPixels);
			bitmap.copyPixelsToBuffer(bitmapBuffer);

			
			int bitmapWidth = bitmap.getWidth();
			int lumaIndex = 0;
			for(int y = region.top; y < region.bottom; y++)
			{
				for(int x = region.left; x < region.right; x++)
				{
					int offset = x + y*bitmapWidth;
					int color = bitmapPixels[offset];
					
					int red   = rbTable[(color>>11)&31];
					int green = gTable[(color>>5)&63];
					int blue  = rbTable[color&31];
				
					// Calc Luma
					float fVal = 0.299f * (float)red + 0.587f * (float)green + 0.114f * (float)blue;
					
					int iVal = (int)Math.round(fVal);
					
					lumaPixels[lumaIndex] = (byte) (iVal & 0xFF);
					lumaIndex ++;
				} 
			}
			break;
		}		
		case ARGB_8888:
		{
//			int bitmapSize = bitmap.getWidth()*bitmap.getHeight();
//			int[] bitmapPixels = new int[bitmapSize];
//			IntBuffer bitmapBuffer = IntBuffer.wrap(bitmapPixels);
//			bitmap.copyPixelsToBuffer(bitmapBuffer);
//			int bitmapWidth = bitmap.getWidth();
			int lumaIndex = 0;
			for(int y = region.top; y < region.bottom; y++)
			{
				for(int x = region.left; x < region.right; x++)
				{
//					int offset = x + y*bitmapWidth;
//					int p = bitmapPixels[offset];
					int p = bitmap.getPixel(x, y);
					
					int r = (p & 0xff0000) >> 16;
					int g = (p & 0xff00) >> 8;
					int b = p & 0xff;
				
					// Calc Luma
					float fVal = 0.299f * (float)r + 0.587f * (float)g + 0.114f * (float)b;
					int iVal = (int)Math.round(fVal);
					
					lumaPixels[lumaIndex] = (byte) (iVal & 0xFF);
					lumaIndex ++;
				}
			}
			break;
		}
		default:
			throw new IllegalArgumentException("Unsupported image type: " + config);
		}
		
		Log.v("", "Image.getLumaImage " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	
	public static void getLumaImage(Bitmap bitmap, Image16 luminance)
	{
		long startTime = System.currentTimeMillis();
		
		int size = bitmap.getWidth()*bitmap.getHeight();
		
		// Check size and target size
		short[] luminanceData = luminance.pixels;

		Bitmap.Config config = bitmap.getConfig();
		switch(config)
		{
		case RGB_565:
		{
			// Build conversion table
			int[] rbTable = new int[32];
			int[] gTable = new int[64];
			for (int i=0; i<32; i++)
				rbTable[i] = 255*i/31;
			for (int i=0; i<64; i++)
				gTable[i] = 255*i/63;
			
			ShortBuffer pixels = ShortBuffer.allocate(size);
			bitmap.copyPixelsToBuffer(pixels);
			for (int i = 0; i < size; i++) {
				int color = pixels.get(i);
				
				int red   = rbTable[(color>>11)&31];
				int green = gTable[(color>>5)&63];
				int blue  = rbTable[color&31];
				
				// Calc Luma
				float fVal = 0.299f * (float)red + 0.587f * (float)green + 0.114f * (float)blue;
				luminanceData[i] = (short)Math.round(fVal);
				
				//Log.v("", "val:"+luminanceData[i]);
			}
			break;
		}
		case ARGB_8888:
		{
			ShortBuffer pixels = ShortBuffer.wrap(luminance.pixels);
			bitmap.copyPixelsToBuffer(pixels);
			
			for (int i = 0; i < size; i++) {
				int p = luminance.pixels[i];
				int r = (p & 0xff0000) >> 16;
				int g = (p & 0xff00) >> 8;
				int b = p & 0xff;
				
				// Calc Luma
				float fVal = 0.299f * (float)r + 0.587f * (float)g + 0.114f * (float)b;
//				int iVal = Math.round(fVal);
				int iVal = (int)fVal;
				luminance.pixels[i] = (short)iVal;
				
				//Log.v("", "val:"+luminanceData[i]);
			}
			break;
		}
		default:
			throw new IllegalArgumentException("Unsupported image type: " + config);
		}
		
		Log.v("", "Image.getLumaImage " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
	public static void convertLumaToARGB(Image32 image)
	{
		long startTime = System.currentTimeMillis();
		
		int[] pixels = image.pixels;
		int size = image.pixels.length;
		for (int i = 0; i < size; i++) {
			int iVal = pixels[i];
			pixels[i] = 0xff000000 | iVal << 16 | iVal << 8 | iVal;	
		}
		
		Log.v("", "Image.convertLumaToARGB " + (System.currentTimeMillis() - startTime) + "ms");
	}
	
//	public static void loadARGBFromLuminance(Image32 targetImage, Image32 lumImage)
//	{
//		// check target source sizes
//		
//		int[] targetData = targetImage.pixels;
//		int[] sourceData = lumImage.pixels;
//		int v = 0;
//		for (int i = 0; i < targetData.length; i++) {
//			int iVal = sourceData[i];
//			targetData[i] = 0xff000000 | iVal << 16 | iVal << 8 | iVal;	
//		}
//	}
	
//	public static void thresholdLum(Image32 sourceImage, int min, int max, Image32 targetImage)
//	{
//		// check target source sizes
//		
//		int[] sourceData = sourceImage.pixels;
//		int[] targetData = targetImage.pixels;
//		for (int i = 0; i < targetData.length; i++) {
//			int iVal = sourceData[i];
//			if(iVal >= min && iVal <= max)
//			{
//				targetData[i] = sourceData[i];
//			}
//			else
//			{
//				targetData[i] = 0;
//			}
//		}
//	}
	

}
